home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 10868 / 10868.xpi / modules / ext / Preferences.js < prev    next >
Text File  |  2010-02-02  |  18KB  |  526 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Preferences.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Myk Melez <myk@mozilla.org>
  22.  *   Daniel Aquino <mr.danielaquino@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. let EXPORTED_SYMBOLS = ["Preferences"];
  39.  
  40. const Cc = Components.classes;
  41. const Ci = Components.interfaces;
  42. const Cr = Components.results;
  43. const Cu = Components.utils;
  44.  
  45. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  46.  
  47. // The minimum and maximum integers that can be set as preferences.
  48. // The range of valid values is narrower than the range of valid JS values
  49. // because the native preferences code treats integers as NSPR PRInt32s,
  50. // which are 32-bit signed integers on all platforms.
  51. const MAX_INT = Math.pow(2, 31) - 1;
  52. const MIN_INT = -MAX_INT;
  53.  
  54. function Preferences(args) {
  55.     if (isObject(args)) {
  56.       if (args.branch)
  57.         this._prefBranch = args.branch;
  58.       if (args.site)
  59.         this._site = args.site;
  60.     }
  61.     else if (args)
  62.       this._prefBranch = args;
  63. }
  64.  
  65. Preferences.prototype = {
  66.   /**
  67.    * Get the value of a pref, if any; otherwise return the default value.
  68.    *
  69.    * @param   prefName  {String|Array}
  70.    *          the pref to get, or an array of prefs to get
  71.    *
  72.    * @param   defaultValue
  73.    *          the default value, if any, for prefs that don't have one
  74.    *
  75.    * @returns the value of the pref, if any; otherwise the default value
  76.    */
  77.   get: function(prefName, defaultValue) {
  78.     if (isArray(prefName))
  79.       return prefName.map(function(v) this.get(v, defaultValue), this);
  80.  
  81.     if (this._site)
  82.       return this._siteGet(prefName, defaultValue);
  83.     else
  84.       return this._get(prefName, defaultValue);
  85.   },
  86.  
  87.   _get: function(prefName, defaultValue) {
  88.     switch (this._prefSvc.getPrefType(prefName)) {
  89.       case Ci.nsIPrefBranch.PREF_STRING:
  90.         return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data;
  91.  
  92.       case Ci.nsIPrefBranch.PREF_INT:
  93.         return this._prefSvc.getIntPref(prefName);
  94.  
  95.       case Ci.nsIPrefBranch.PREF_BOOL:
  96.         return this._prefSvc.getBoolPref(prefName);
  97.  
  98.       case Ci.nsIPrefBranch.PREF_INVALID:
  99.         return defaultValue;
  100.  
  101.       default:
  102.         // This should never happen.
  103.         throw "Error getting pref " + prefName + "; its value's type is " +
  104.               this._prefSvc.getPrefType(prefName) + ", which I don't know " +
  105.               "how to handle.";
  106.     }
  107.   },
  108.  
  109.   _siteGet: function(prefName, defaultValue) {
  110.     let value = this._contentPrefSvc.getPref(this._site, this._prefBranch + prefName);
  111.     return typeof value != "undefined" ? value : defaultValue;
  112.   },
  113.  
  114.   /**
  115.    * Set a preference to a value.
  116.    *
  117.    * You can set multiple prefs by passing an object as the only parameter.
  118.    * In that case, this method will treat the properties of the object
  119.    * as preferences to set, where each property name is the name of a pref
  120.    * and its corresponding property value is the value of the pref.
  121.    *
  122.    * @param   prefName  {String|Object}
  123.    *          the name of the pref to set; or an object containing a set
  124.    *          of prefs to set
  125.    *
  126.    * @param   prefValue {String|Number|Boolean}
  127.    *          the value to which to set the pref
  128.    *
  129.    * Note: Preferences cannot store non-integer numbers or numbers outside
  130.    * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
  131.    * store it as a string by calling toString() on the number before passing
  132.    * it to this method, i.e.:
  133.    *   Preferences.set("pi", 3.14159.toString())
  134.    *   Preferences.set("big", Math.pow(2, 31).toString()).
  135.    */
  136.   set: function(prefName, prefValue) {
  137.     if (isObject(prefName)) {
  138.       for (let [name, value] in Iterator(prefName))
  139.         this.set(name, value);
  140.       return;
  141.     }
  142.  
  143.     if (this._site)
  144.       this._siteSet(prefName, prefValue);
  145.     else
  146.       this._set(prefName, prefValue);
  147.   },
  148.  
  149.   _set: function(prefName, prefValue) {
  150.     let prefType;
  151.     if (typeof prefValue != "undefined" && prefValue != null)
  152.       prefType = prefValue.constructor.name;
  153.  
  154.     switch (prefType) {
  155.       case "String":
  156.         {
  157.           let string = Cc["@mozilla.org/supports-string;1"].
  158.                        createInstance(Ci.nsISupportsString);
  159.           string.data = prefValue;
  160.           this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string);
  161.         }
  162.         break;
  163.  
  164.       case "Number":
  165.         // We throw if the number is outside the range, since the result
  166.         // will never be what the consumer wanted to store, but we only warn
  167.         // if the number is non-integer, since the consumer might not mind
  168.         // the loss of precision.
  169.         if (prefValue > MAX_INT || prefValue < MIN_INT)
  170.           throw("you cannot set the " + prefName + " pref to the number " +
  171.                 prefValue + ", as number pref values must be in the signed " +
  172.                 "32-bit integer range -(2^31-1) to 2^31-1.  To store numbers " +
  173.                 "outside that range, store them as strings.");
  174.         this._prefSvc.setIntPref(prefName, prefValue);
  175.         if (prefValue % 1 != 0)
  176.           Cu.reportError("Warning: setting the " + prefName + " pref to the " +
  177.                          "non-integer number " + prefValue + " converted it " +
  178.                          "to the integer number " + this.get(prefName) +
  179.                          "; to retain fractional precision, store non-integer " +
  180.                          "numbers as strings.");
  181.         break;
  182.  
  183.       case "Boolean":
  184.         this._prefSvc.setBoolPref(prefName, prefValue);
  185.         break;
  186.  
  187.       default:
  188.         throw "can't set pref " + prefName + " to value '" + prefValue +
  189.               "'; it isn't a String, Number, or Boolean";
  190.     }
  191.   },
  192.  
  193.   _siteSet: function(prefName, prefValue) {
  194.     this._contentPrefSvc.setPref(this._site, this._prefBranch + prefName, prefValue);
  195.   },
  196.  
  197.   /**
  198.    * Whether or not the given pref has a value.  This is different from isSet
  199.    * because it returns true whether the value of the pref is a default value
  200.    * or a user-set value, while isSet only returns true if the value
  201.    * is a user-set value.
  202.    *
  203.    * @param   prefName  {String|Array}
  204.    *          the pref to check, or an array of prefs to check
  205.    *
  206.    * @returns {Boolean|Array}
  207.    *          whether or not the pref has a value; or, if the caller provided
  208.    *          an array of pref names, an array of booleans indicating whether
  209.    *          or not the prefs have values
  210.    */
  211.   has: function(prefName) {
  212.     if (isArray(prefName))
  213.       return prefName.map(this.has, this);
  214.  
  215.     if (this._site)
  216.       return this._siteHas(prefName);
  217.     else
  218.       return this._has(prefName);
  219.   },
  220.  
  221.   _has: function(prefName) {
  222.     return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
  223.   },
  224.  
  225.   _siteHas: function(prefName) {
  226.     return this._contentPrefSvc.hasPref(this._site, this._prefBranch + prefName);
  227.   },
  228.  
  229.   /**
  230.    * Whether or not the given pref has a user-set value.  This is different
  231.    * from |has| because it returns true only if the value of the pref is a user-
  232.    * set value, while |has| returns true if the value of the pref is a default
  233.    * value or a user-set value.
  234.    *
  235.    * @param   prefName  {String|Array}
  236.    *          the pref to check, or an array of prefs to check
  237.    *
  238.    * @returns {Boolean|Array}
  239.    *          whether or not the pref has a user-set value; or, if the caller
  240.    *          provided an array of pref names, an array of booleans indicating
  241.    *          whether or not the prefs have user-set values
  242.    */
  243.   isSet: function(prefName) {
  244.     if (isArray(prefName))
  245.       return prefName.map(this.isSet, this);
  246.  
  247.     return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName));
  248.   },
  249.  
  250.   /**
  251.    * Whether or not the given pref has a user-set value. Use isSet instead,
  252.    * which is equivalent.
  253.    * @deprecated
  254.    */
  255.   modified: function(prefName) { return this.isSet(prefName) },
  256.  
  257.   reset: function(prefName) {
  258.     if (isArray(prefName)) {
  259.       prefName.map(function(v) this.reset(v), this);
  260.       return;
  261.     }
  262.  
  263.     if (this._site)
  264.       this._siteReset(prefName);
  265.     else
  266.       this._reset(prefName);
  267.   },
  268.  
  269.   _reset: function(prefName) {
  270.     try {
  271.       this._prefSvc.clearUserPref(prefName);
  272.     }
  273.     catch(ex) {
  274.       // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
  275.       // to reset a pref that doesn't exist or is already set to its default
  276.       // value.  This interface fails silently in those cases, so callers
  277.       // can unconditionally reset a pref without having to check if it needs
  278.       // resetting first or trap exceptions after the fact.  It passes through
  279.       // other exceptions, however, so callers know about them, since we don't
  280.       // know what other exceptions might be thrown and what they might mean.
  281.       if (ex.result != Cr.NS_ERROR_UNEXPECTED)
  282.         throw ex;
  283.     }
  284.   },
  285.  
  286.   _siteReset: function(prefName) {
  287.     return this._contentPrefSvc.removePref(this._site, this._prefBranch + prefName);
  288.   },
  289.  
  290.   /**
  291.    * Lock a pref so it can't be changed.
  292.    *
  293.    * @param   prefName  {String|Array}
  294.    *          the pref to lock, or an array of prefs to lock
  295.    */
  296.   lock: function(prefName) {
  297.     if (isArray(prefName))
  298.       prefName.map(this.lock, this);
  299.  
  300.     this._prefSvc.lockPref(prefName);
  301.   },
  302.  
  303.   /**
  304.    * Unlock a pref so it can be changed.
  305.    *
  306.    * @param   prefName  {String|Array}
  307.    *          the pref to lock, or an array of prefs to lock
  308.    */
  309.   unlock: function(prefName) {
  310.     if (isArray(prefName))
  311.       prefName.map(this.unlock, this);
  312.  
  313.     this._prefSvc.unlockPref(prefName);
  314.   },
  315.  
  316.   /**
  317.    * Whether or not the given pref is locked against changes.
  318.    *
  319.    * @param   prefName  {String|Array}
  320.    *          the pref to check, or an array of prefs to check
  321.    *
  322.    * @returns {Boolean|Array}
  323.    *          whether or not the pref has a user-set value; or, if the caller
  324.    *          provided an array of pref names, an array of booleans indicating
  325.    *          whether or not the prefs have user-set values
  326.    */
  327.   locked: function(prefName) {
  328.     if (isArray(prefName))
  329.       return prefName.map(this.locked, this);
  330.  
  331.     return this._prefSvc.prefIsLocked(prefName);
  332.   },
  333.  
  334.   /**
  335.    * Start observing a pref.
  336.    *
  337.    * The callback can be a function or any object that implements nsIObserver.
  338.    * When the callback is a function and thisObject is provided, it gets called
  339.    * as a method of thisObject.
  340.    *
  341.    * @param   prefName    {String}
  342.    *          the name of the pref to observe
  343.    *
  344.    * @param   callback    {Function|Object}
  345.    *          the code to notify when the pref changes;
  346.    *
  347.    * @param   thisObject  {Object}  [optional]
  348.    *          the object to use as |this| when calling a Function callback;
  349.    *
  350.    * @returns the wrapped observer
  351.    */
  352.   observe: function(prefName, callback, thisObject) {
  353.     let fullPrefName = this._prefBranch + (prefName || "");
  354.  
  355.     let observer = new PrefObserver(fullPrefName, callback, thisObject);
  356.     Preferences._prefSvc.addObserver(fullPrefName, observer, true);
  357.     observers.push(observer);
  358.  
  359.     return observer;
  360.   },
  361.  
  362.   /**
  363.    * Stop observing a pref.
  364.    *
  365.    * You must call this method with the same prefName, callback, and thisObject
  366.    * with which you originally registered the observer.  However, you don't have
  367.    * to call this method on the same exact instance of Preferences; you can call
  368.    * it on any instance.  For example, the following code first starts and then
  369.    * stops observing the "foo.bar.baz" preference:
  370.    *
  371.    *   let observer = function() {...};
  372.    *   Preferences.observe("foo.bar.baz", observer);
  373.    *   new Preferences("foo.bar.").ignore("baz", observer);
  374.    *
  375.    * @param   prefName    {String}
  376.    *          the name of the pref being observed
  377.    *
  378.    * @param   callback    {Function|Object}
  379.    *          the code being notified when the pref changes
  380.    *
  381.    * @param   thisObject  {Object}  [optional]
  382.    *          the object being used as |this| when calling a Function callback
  383.    */
  384.   ignore: function(prefName, callback, thisObject) {
  385.     let fullPrefName = this._prefBranch + (prefName || "");
  386.  
  387.     // This seems fairly inefficient, but I'm not sure how much better we can
  388.     // make it.  We could index by fullBranch, but we can't index by callback
  389.     // or thisObject, as far as I know, since the keys to JavaScript hashes
  390.     // (a.k.a. objects) can apparently only be primitive values.
  391.     let [observer] = observers.filter(function(v) v.prefName   == fullPrefName &&
  392.                                                   v.callback   == callback &&
  393.                                                   v.thisObject == thisObject);
  394.  
  395.     if (observer) {
  396.       Preferences._prefSvc.removeObserver(fullPrefName, observer);
  397.       observers.splice(observers.indexOf(observer), 1);
  398.     }
  399.   },
  400.  
  401.   resetBranch: function(prefBranch) {
  402.     try {
  403.       this._prefSvc.resetBranch(prefBranch);
  404.     }
  405.     catch(ex) {
  406.       // The current implementation of nsIPrefBranch in Mozilla
  407.       // doesn't implement resetBranch, so we do it ourselves.
  408.       if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
  409.         this.reset(this._prefSvc.getChildList(prefBranch, []));
  410.       else
  411.         throw ex;
  412.     }
  413.   },
  414.  
  415.   /**
  416.    * The branch of the preferences tree to which this instance provides access.
  417.    * @private
  418.    */
  419.   _prefBranch: "",
  420.  
  421.   site: function(site) {
  422.     if (!(site instanceof Ci.nsIURI))
  423.       site = this._ioSvc.newURI("http://" + site, null, null);
  424.     return new Preferences({ branch: this._prefBranch, site: site });
  425.   },
  426.  
  427.   /**
  428.    * Preferences Service
  429.    * @private
  430.    */
  431.   get _prefSvc() {
  432.     let prefSvc = Cc["@mozilla.org/preferences-service;1"].
  433.                   getService(Ci.nsIPrefService).
  434.                   getBranch(this._prefBranch).
  435.                   QueryInterface(Ci.nsIPrefBranch2);
  436.     this.__defineGetter__("_prefSvc", function() prefSvc);
  437.     return this._prefSvc;
  438.   },
  439.  
  440.   /**
  441.    * IO Service
  442.    * @private
  443.    */
  444.   get _ioSvc() {
  445.     let ioSvc = Cc["@mozilla.org/network/io-service;1"].
  446.                 getService(Ci.nsIIOService);
  447.     this.__defineGetter__("_ioSvc", function() ioSvc);
  448.     return this._ioSvc;
  449.   },
  450.  
  451.   /**
  452.    * Site Preferences Service
  453.    * @private
  454.    */
  455.   get _contentPrefSvc() {
  456.     let contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
  457.                          getService(Ci.nsIContentPrefService);
  458.     this.__defineGetter__("_contentPrefSvc", function() contentPrefSvc);
  459.     return this._contentPrefSvc;
  460.   }
  461.  
  462. };
  463.  
  464. // Give the constructor the same prototype as its instances, so users can access
  465. // preferences directly via the constructor without having to create an instance
  466. // first.
  467. Preferences.__proto__ = Preferences.prototype;
  468.  
  469. /**
  470.  * A cache of pref observers.
  471.  *
  472.  * We use this to remove observers when a caller calls Preferences::ignore.
  473.  *
  474.  * All Preferences instances share this object, because we want callers to be
  475.  * able to remove an observer using a different Preferences object than the one
  476.  * with which they added it.  That means we have to identify the observers
  477.  * in this object by their complete pref name, not just their name relative to
  478.  * the root branch of the Preferences object with which they were created.
  479.  */
  480. let observers = [];
  481.  
  482. function PrefObserver(prefName, callback, thisObject) {
  483.   this.prefName = prefName;
  484.   this.callback = callback;
  485.   this.thisObject = thisObject;
  486. }
  487.  
  488. PrefObserver.prototype = {
  489.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  490.  
  491.   observe: function(subject, topic, data) {
  492.     // The pref service only observes whole branches, but we only observe
  493.     // individual preferences, so we check here that the pref that changed
  494.     // is the exact one we're observing (and not some sub-pref on the branch).
  495.     if (data != this.prefName)
  496.       return;
  497.  
  498.     if (typeof this.callback == "function") {
  499.       let prefValue = Preferences.get(this.prefName);
  500.  
  501.       if (this.thisObject)
  502.         this.callback.call(this.thisObject, prefValue);
  503.       else
  504.         this.callback(prefValue);
  505.     }
  506.     else // typeof this.callback == "object" (nsIObserver)
  507.       this.callback.observe(subject, topic, data);
  508.   }
  509. };
  510.  
  511. function isArray(val) {
  512.   // We can't check for |val.constructor == Array| here, since the value
  513.   // might be from a different context whose Array constructor is not the same
  514.   // as ours, so instead we match based on the name of the constructor.
  515.   return (typeof val != "undefined" && val != null && typeof val == "object" &&
  516.           val.constructor.name == "Array");
  517. }
  518.  
  519. function isObject(val) {
  520.   // We can't check for |val.constructor == Object| here, since the value
  521.   // might be from a different context whose Object constructor is not the same
  522.   // as ours, so instead we match based on the name of the constructor.
  523.   return (typeof val != "undefined" && val != null && typeof val == "object" &&
  524.           val.constructor.name == "Object");
  525. }
  526.